﻿using System;
using System.Collections.Generic;

namespace TLang.Parsers
{
    using Ast;

    public class Parser
    {
        public static Node Parse(string text, String file)
        {
            PreParser preparser = new PreParser(text, file);
            Node prenode = preparser.Parse();
            return ParseNode(prenode);
        }

        public static Node ParseNode(Node prenode)
        {

            if (!(prenode is Tuple))
            {
                // Case 1: node is not of form (..) or [..], return the node itself
                return prenode;
            }
            else
            {
                // Case 2: node is of form (..) or [..]
                Tuple tuple = ((Tuple)prenode);
                List<Node> elements = tuple.Elements;

                if (tuple.Open.IsDelimType(Constants.SQUARE_BEGIN))
                {
                    // Case 2.1: node is of form [..]
                    return new VectorLiteral(ParseList(elements), tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
                }
                else if (tuple.Open.IsDelimType(Constants.CURLY_BEGIN))
                {
                    // Case node is form {}
                    return new RecordLiteral(ParseList(elements), tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
                }
                else
                {
                    // Case 2.2: node is (..)
                    if (elements.IsEmpty())
                    {
                        // Case 2.2.1: node is (). This is not allowed
                        throw new ParserException("syntax error", tuple);
                    }
                    else
                    {
                        // Case 2.2.2: node is of form (keyword ..)
                        Node keyNode = elements[0];

                        if (keyNode is Name)
                        {
                            switch (((Name)keyNode).Id)
                            {
                                case Constants.SEQ_KEYWORD:
                                    return ParseBlock(tuple);
                                case Constants.IF_KEYWORD:
                                    return ParseIf(tuple);
                                case Constants.DEF_KEYWORD:
                                    return ParseDef(tuple);
                                case Constants.ASSIGN_KEYWORD:
                                    return ParseAssign(tuple);
                                case Constants.DECLARE_KEYWORD:
                                    return ParseDeclare(tuple);
                                case Constants.FUN_KEYWORD:
                                    return ParseFun(tuple);
                                case Constants.RECORD_KEYWORD:
                                    return ParseRecordDef(tuple);
                                default:
                                    return ParseCall(tuple);
                            }
                        }
                        else
                        {
                            // applications whose operator is not a name
                            // e.g. ((foo 1) 2)
                            return ParseCall(tuple);
                        }
                    }
                }
            }
        }


        public static Block ParseBlock(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            List<Node> statements = ParseList(elements.SubList(1, elements.Count));
            return new Block(statements, tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static If ParseIf(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count != 4)
            {
                throw new ParserException("incorrect format of if", tuple);
            }
            Node test = ParseNode(elements[1]);
            Node conseq = ParseNode(elements[2]);
            Node alter = ParseNode(elements[3]);
            return new If(test, conseq, alter, tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static Def ParseDef(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count != 3)
            {
                throw new ParserException("incorrect format of definition", tuple);
            }
            Node pattern = ParseNode(elements[1]);
            Node value = ParseNode(elements[2]);
            return new Def(pattern, value, tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);

        }


        public static Assign ParseAssign(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count != 3)
            {
                throw new ParserException("incorrect format of definition", tuple);
            }
            Node pattern = ParseNode(elements[1]);
            Node value = ParseNode(elements[2]);
            return new Assign(pattern, value, tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static Declare ParseDeclare(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 2)
            {
                throw new ParserException("syntax error in record type definition", tuple);
            }
            Scope properties = ParseProperties(elements.SubList(1, elements.Count));
            return new Declare(properties, tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static Fun ParseFun(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;

            if (elements.Count < 3)
            {
                throw new ParserException("syntax error in function definition", tuple);
            }

            // construct parameter list
            Node preParams = elements[1];
            if (!(preParams is Tuple))
            {
                throw new ParserException("incorrect format of parameters: " + preParams.ToString(), preParams);
            }

            // parse the parameters, test whether it's all names or all tuples
            bool hasName = false;
            bool hasTuple = false;
            List<Name> paramNames = new List<Name>();
            List<Node> paramTuples = new List<Node>();

            foreach (Node p in ((Tuple)preParams).Elements)
            {
                if (p is Name)
                {
                    hasName = true;
                    paramNames.Add((Name)p);
                }
                else if (p is Tuple)
                {
                    hasTuple = true;
                    List<Node> argElements = ((Tuple)p).Elements;
                    if (argElements.Count == 0)
                    {
                        throw new ParserException("illegal argument format: " + p.ToString(), p);
                    }
                    if (!(argElements[0] is Name))
                    {
                        throw new ParserException("illegal argument name : " + argElements[0], p);
                    }

                    Name name = (Name)argElements[0];
                    if (!name.Id.Equals(Constants.RETURN_ARROW))
                    {
                        paramNames.Add(name);
                    }
                    paramTuples.Add(p);
                }
            }

            if (hasName && hasTuple)
            {
                throw new ParserException("parameters must be either all names or all tuples: " +
                        preParams.ToString(), preParams);
            }

            Scope properties;
            if (hasTuple)
            {
                properties = ParseProperties(paramTuples);
            }
            else
            {
                properties = null;
            }

            // construct body
            List<Node> statements = ParseList(elements.SubList(2, elements.Count));
            int start = statements[0].start;
            int end = statements[statements.Count - 1].end;
            Node body = new Block(statements, tuple.file, start, end, tuple.line, tuple.col);

            return new Fun(paramNames, properties, body,
                    tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static RecordDef ParseRecordDef(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 2)
            {
                throw new ParserException("syntax error in record type definition", tuple);
            }

            Node name = elements[1];
            Node maybeParents = elements[2];

            List<Name> parents;
            List<Node> fields;

            if (!(name is Name))
            {
                throw new ParserException("syntax error in record name: " + name.ToString(), name);
            }

            // check if there are parents (record A (B C) ...)
            if (maybeParents is Tuple &&
                    ((Tuple)maybeParents).Open.IsDelimType(Constants.PAREN_BEGIN))
            {
                List<Node> parentNodes = ((Tuple)maybeParents).Elements;
                parents = new List<Name>();
                foreach (Node p in parentNodes)
                {
                    if (!(p is Name))
                    {
                        throw new ParserException("parents can only be names", p);
                    }
                    parents.Add((Name)p);
                }
                fields = elements.SubList(3, elements.Count);
            }
            else
            {
                parents = null;
                fields = elements.SubList(2, elements.Count);
            }

            Scope properties = ParseProperties(fields);
            return new RecordDef((Name)name, parents, properties, tuple.file,
                    tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static Call ParseCall(Tuple tuple)
        {
            List<Node> elements = tuple.Elements;
            Node func = ParseNode(elements[0]);
            List<Node> parsedArgs = ParseList(elements.SubList(1, elements.Count));
            Argument args = new Argument(parsedArgs);
            return new Call(func, args, tuple.file, tuple.start, tuple.end, tuple.line, tuple.col);
        }


        public static List<Node> ParseList(List<Node> prenodes)
        {
            List<Node> parsed = new List<Node>();
            foreach (Node s in prenodes)
            {
                parsed.Add(ParseNode(s));
            }
            return parsed;
        }


        // treat the list of nodes as key-value pairs like (:x 1 :y 2)
        public static Map<String, Node> ParseMap(List<Node> prenodes)
        {
            Map<String, Node> ret = new Map<string, Node>();
            if (prenodes.Count % 2 != 0)
            {
                throw new ParserException("must be of the form (:key1 value1 :key2 value2), but got: " +
                        prenodes.ToString(), prenodes[0]);
            }

            for (int i = 0; i < prenodes.Count; i += 2)
            {
                Node key = prenodes[i];
                Node value = prenodes[i + 1];
                if (!(key is Keyword))
                {
                    throw new ParserException("key must be a keyword, but got: " + key.ToString(), key);
                }
                ret.Put(((Keyword)key).id, value);
            }
            return ret;
        }


        public static Scope ParseProperties(List<Node> fields)
        {
            Scope properties = new Scope();
            foreach (Node field in fields)
            {
                if (!(field is Tuple &&
                        ((Tuple)field).Open.IsDelimType(Constants.SQUARE_BEGIN) &&
                        ((Tuple)field).Elements.Count >= 2))
                {
                    throw new ParserException("incorrect form of descriptor: " + field.ToString(), field);
                }
                else
                {
                    List<Node> elements = ParseList(((Tuple)field).Elements);
                    Node nameNode = elements[0];
                    if (!(nameNode is Name))
                    {
                        throw new ParserException("expect a name, but got: " + nameNode.ToString(), nameNode);
                    }
                    String id = ((Name)nameNode).Id;
                    if (properties.ContainsKey(id))
                    {
                        throw new ParserException("duplicated name: " + nameNode.ToString(), nameNode);
                    }

                    Node typeNode = elements[1];
                    if (!(typeNode is Name))
                    {
                        throw new ParserException("type must be a name, but got: " + typeNode.ToString(), typeNode);
                    }
                    properties.Put(id, "type", typeNode);

                    Map<String, Node> props = ParseMap(elements.SubList(2, elements.Count));
                    Map<String, Object> propsObj = new Map<string, object>();
                    foreach (var e in props)
                    {
                        propsObj.Put(e.Key, e.Value);
                    }
                    properties.PutProperties(((Name)nameNode).Id, propsObj);
                }
            }
            return properties;
        }
    }

    public static class NodeExtensions
    {
        public static bool IsDelimType(this Node c, String d)
        {
            return c is Delimeter && ((Delimeter)c).Shape.Equals(d);
        }
    }
}
